﻿using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using AudioPlayer.GUI.Theme;
using AudioPlayer.Plugin.Storage;
using AudioPlayer.Storage;
namespace AudioPlayer.GUI.Music {
	internal class MusicTree : Control {
		#region Variables
		#region Style
		private Font _fontGroup;
		private Font _fontNode;

		private LinearGradientBrush _brushNodeBack;

		private List<MusicGroup> _groups;
		#endregion

		private MusicGroup _groupLibrary;
		private MusicGroup _groupPlaylist;
		private MusicNode _lastClick;//Paskutinis itemas paspaustas
		private Timer _effectTimer;
		private TextBox _textBox;
		private float _effectCount;
		private bool _effectDirection;
		private MusicContext _context;
		#endregion
		#region Constructor
		public MusicTree() {
			//Don't allow to do any stuff 
			//when in design mode.
			if (!this.DesignMode) {
				Init();
				InitStyle();
				InitPlaylist();
				this.SetStyle(ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
			}
		}
		#endregion
		#region Private Voids
		private void Init() {
			_groups = new List<MusicGroup>();

			_effectCount = 1;
			_effectTimer = new Timer();
			_effectTimer.Interval = 25;
			_effectTimer.Tick += new EventHandler(TimerTick);

			this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
		}	
		private void InitStyle() {
			_fontNode = new Font("Verdana", 8);
			_fontGroup = new Font("Verdana", 8, FontStyle.Bold);
			this.BackColor = ThemeManager.ActiveColorTable.MusicTreeGroupBackground1;
		}
		private void InitPlaylist() {
			//Create groups
			_groupLibrary = new MusicGroup("Library");
			_groupPlaylist = new MusicGroup("Playlists");
			
			//Set there expanded state
			_groupLibrary.Expanded = this.ExpandedLibrary;
			_groupPlaylist.Expanded = this.ExpandedPlaylist;
			
			//Group events
			_groupLibrary.StateChanged += new EventHandler(GroupLibrary_StateChanged);
			_groupPlaylist.StateChanged += new EventHandler(GroupPlaylist_StateChanged);
			
			//Add to list.
			this.Groups.Add(_groupLibrary);
			this.Groups.Add(_groupPlaylist);
			
			MusicNode music = new MusicNode("Music", false);
			MusicNode favorite = new MusicNode("Favorite", false);
			music.Selected = true;
			_groupLibrary.Nodes.Add(music);
			_groupPlaylist.Nodes.Add(favorite);			
			this.LastClick = music;
			music.MouseClick += new MouseEventHandler(Music_MouseClick);
			favorite.MouseClick += new MouseEventHandler(Favorite_MouseClick);
			//Let's make sure we have storage plugin
			if (Config.Plugins.HasStorage) {
				if (Config.Library.Playlists != null && Config.Library.Playlists.Count > 0) {
					foreach (IPlaylist pl in Config.Library.Playlists) {
						MusicNode nodePl = new MusicNode(pl.Name, true);
						nodePl.Tag = pl;
						this.SubscribeNode(nodePl);
						_groupPlaylist.Nodes.Add(nodePl);
					}
				}
			}
		}		
		/// <summary>
		/// Recalculates bounds of each node.
		/// </summary>
		private void Rebound() {
			if (_groups != null && _groups.Count > 0) {
				//Normal style
				if (this.Expanded) {
					int y = 0;
					for (int i = 0; i < _groups.Count; i++) {
						_groups[i].Bounds = new Rectangle(0, y, this.Width, 20);
						y += 20;
						if (_groups[i].Expanded) {
							for (int n = 0; n < _groups[i].Nodes.Count; n++) {
								_groups[i].Nodes[n].Bounds = new Rectangle(0, y, this.Width, 20);
								y += 20;
							}
						}
					}
				} else {
					//Collapsed style
					int y = 0;
					for (int i = 0; i < _groups.Count; i++) {
						if (_groups[i].Nodes != null && _groups[i].Nodes.Count > 0) {
							for (int n = 0; n < _groups[i].Nodes.Count; n++) {
								_groups[i].Nodes[n].Bounds = new Rectangle(0, y, 20, 60);
								y += 60;
							}
						}
					}
				}
			}
		}
		private void Draw(Graphics g) {
			//Double buffer
			int w = this.Width;
			int h = this.TotalHeight;
			if (w > 0 && h > 0) {
				Bitmap bmp = new Bitmap(w, h);
				Graphics dbl = Graphics.FromImage(bmp);
				dbl.Clear(ThemeManager.ActiveColorTable.MusicTreeBackground);
				for (int i = 0; i < _groups.Count; i++) {
					this.DrawGroup(dbl, _groups[i]);
					if (!this.Expanded || _groups[i].Expanded) {
						for (int n = 0; n < _groups[i].Nodes.Count; n++) {
							this.DrawNode(dbl, _groups[i].Nodes[n]);
						}
					}
				}
				//Redraw
				g.DrawImage(bmp, new Point(0, 0));
				dbl.Dispose();
				bmp.Dispose();
			}
		}
		private void DrawGroup(Graphics g, MusicGroup group) {
			if (this.Expanded) {
				SolidBrush solidBg = new SolidBrush(ThemeManager.ActiveColorTable.MusicTreeGroupBackground1);
				SolidBrush solidText = new SolidBrush(ThemeManager.ActiveColorTable.MusicTreeGroupTextNormal);
				g.FillRectangle(solidBg, group.Bounds);
				g.DrawString(group.Text, _fontGroup, solidText, new Point(group.Bounds.X, group.Bounds.Y + 3));

				//Release resources
				solidBg.Dispose();
				solidText.Dispose();
			}
		}
		private void DrawNode(Graphics g, MusicNode node) {
			if (node != null && node.Bounds != Rectangle.Empty && (node.Visible || !this.Expanded)) {
				//Dooouuuubbbbllllleeeee buffer
				Bitmap doubleBuffer = new Bitmap(node.Bounds.Width, node.Bounds.Height);
				Graphics graphicsBuffer = Graphics.FromImage(doubleBuffer);
				Rectangle drawRect = new Rectangle(0, 0, node.Bounds.Width, node.Bounds.Height);
				SolidBrush solidBg = new SolidBrush(ThemeManager.ActiveColorTable.MusicTreeBackground);
				SolidBrush solidText = new SolidBrush((node.Selected ? ThemeManager.ActiveColorTable.MusicTreeNodeTextSelect : ThemeManager.ActiveColorTable.MusicTreeNodeTextNormal));
				StringFormat stringF = new StringFormat(StringFormatFlags.DirectionVertical);

				graphicsBuffer.FillRectangle(solidBg, drawRect);

				Pen penBorder = new Pen(ThemeManager.ActiveColorTable.MusicTreeNodeBorder);
				if (node.Selected) {
					_brushNodeBack = new LinearGradientBrush(drawRect, ThemeManager.ActiveColorTable.MusicTreeNodeBackground1, ThemeManager.ActiveColorTable.MusicTreeNodeBackground2, LinearGradientMode.Vertical);
					_brushNodeBack.SetBlendTriangularShape(_effectCount);
					graphicsBuffer.FillRectangle(_brushNodeBack, drawRect);
					if (this.Expanded || node.Bounds.Y > 0) {
						graphicsBuffer.DrawLine(penBorder, 0, 0, drawRect.Width, 0);
					}
					graphicsBuffer.DrawLine(penBorder, 0, drawRect.Height - 1, drawRect.Width, drawRect.Height - 1);
					//Clean up
					_brushNodeBack.Dispose();
					_brushNodeBack = null;
				}
				if (this.Expanded) {
					graphicsBuffer.DrawString(node.Text, _fontNode, solidText, 20, 3);
				} else {
					graphicsBuffer.DrawString(node.Text, _fontNode, solidText, 2, 4,stringF);
				}
				if (node.Selected) {
					if (node.ImageSelect != null) {
						graphicsBuffer.DrawImage(node.ImageSelect, new Point(2, 2));
					}
				} else {
					if (node.ImageNormal != null) {
						graphicsBuffer.DrawImage(node.ImageNormal, new Point(2, 2));
					}
				}

				g.DrawImage(doubleBuffer, node.Bounds);
				//Clean up
				penBorder.Dispose();
				solidBg.Dispose();
				solidText.Dispose();

				graphicsBuffer.Dispose();
				doubleBuffer.Dispose();

				//Set to nulls
				penBorder = null;
				solidBg = null;
				solidText = null;
				graphicsBuffer = null;
				doubleBuffer = null;
			}
		}
		private Bitmap DrawNodeToBitmap( MusicNode node) {
			Bitmap bmp = new Bitmap(node.Bounds.Width, node.Bounds.Height);
			Graphics graphicsBuffer = Graphics.FromImage(bmp);
			Rectangle drawRect = new Rectangle(0, 0, node.Bounds.Width, node.Bounds.Height);
			graphicsBuffer.FillRectangle(new SolidBrush(ThemeManager.ActiveColorTable.MusicTreeBackground), drawRect);
			Pen penBorder = new Pen(ThemeManager.ActiveColorTable.MusicTreeNodeBorder);
			if (node.Selected) {
				_brushNodeBack = new LinearGradientBrush(drawRect, ThemeManager.ActiveColorTable.MusicTreeNodeBackground1, ThemeManager.ActiveColorTable.MusicTreeNodeBackground2, LinearGradientMode.Vertical);
				_brushNodeBack.SetBlendTriangularShape(_effectCount);
				graphicsBuffer.FillRectangle(_brushNodeBack, drawRect);
				graphicsBuffer.DrawLine(penBorder, 0, 0, drawRect.Width, 0);
				graphicsBuffer.DrawLine(penBorder, 0, drawRect.Height - 1, drawRect.Width, drawRect.Height - 1);
			}
			//dispose
			graphicsBuffer.Dispose();
			penBorder.Dispose();
			//return
			return bmp;
		}
		private void EffectProvider() {
			if (_effectDirection) {
				if ((_effectCount + 0.01f) < 1.0f) {
					_effectCount += 0.01f;
				} else {
					//_effectCount = 1;
					_effectDirection = false;
				}
			} else {
				if ((_effectCount - 0.01f) > 0) {
					_effectCount -= 0.01f;
				} else {
					//_effectCount = 0;
					_effectDirection = true;
				}
			}
			//Create temp graphics
			Graphics g = this.CreateGraphics();
			//Draw node
			DrawNode(g, this.LastClick);
			//Dispose graphics
			g.Dispose();
		}
		private void EffectStart() {
			if (Config.Settings.ShowEffects) {
				_effectTimer.Start();
			}
		}
		private void EffectStop() {
			_effectTimer.Stop();
		}

		private void CreateTextBox() {
			if (_textBox == null) {
				_textBox = new TextBox();
			}
			//Add textbox
			this.Controls.Add(_textBox);
			//Set normal properties
			_textBox.BorderStyle = BorderStyle.None;
			_textBox.Location = new Point(this.LastClick.Bounds.Location.X+20,this.LastClick.Bounds.Location.Y+3);
			_textBox.Size = new Size(this.LastClick.Bounds.Size.Width-20,this.LastClick.Bounds.Height-6);
			_textBox.Text = this.LastClick.Text;
			_textBox.BackColor = ThemeManager.ActiveColorTable.MusicTreeNodeBackground1;
			_textBox.ForeColor = ThemeManager.ActiveColorTable.MusicTreeNodeTextSelect;
			_textBox.Font = _fontNode;
			//If loooses focus, update text
			_textBox.LostFocus += new EventHandler(TextBox_LostFocus);
			_textBox.KeyDown += new KeyEventHandler(TextBox_KeyDown);


			//activate
			_textBox.SelectAll();
			_textBox.Focus();
		}
		private void CreateContext() {
			this.CreateContext(true, true);
		}
		private void CreateContext(bool add, bool delete) {
			_context = new MusicContext();
			//Events
			_context.ItemNew.Click += new EventHandler(ItemNew_Click);
			_context.ItemDelete.Click += new EventHandler(ItemDelete_Click);
			_context.Closed += new ToolStripDropDownClosedEventHandler(Context_Closed);
			//visibilities
			_context.ItemNew.Visible = add;
			_context.ItemDelete.Visible = delete;
			_context.ItemSeperatorFirst.Visible = delete;
		}
		
		private void UpdateText() {
			this.EffectStart();
			if (this.LastClick.Tag is IPlaylist) {
				this.LastClick.Text = _textBox.Text;
				IPlaylist pl = (IPlaylist)this.LastClick.Tag;
				pl.Name = _textBox.Text;

				Config.Plugins.Storage.Update(pl);
				_textBox.Dispose();
				_textBox = null;
			}
		}

		private void SubscribeNode(MusicNode node) {
			if (node != null) {
				node.MouseClick += new MouseEventHandler(Node_MouseClick);
			}
		}
		#endregion
		#region Public Voids
		/// <summary>
		/// Renames current selected node.
		/// </summary>
		public void Rename() {
			if (this.LastClick != null && this.LastClick.Editable) {
				this.EffectStop();
				this.CreateTextBox();
			}
		}

		/// <summary>
		/// Selects the node.
		/// </summary>
		/// <param name="node">The node.</param>
		public void SelectNode(MusicNode node) {
			Graphics g = this.CreateGraphics();
			if (this.LastClick != null) {
				this.LastClick.Selected = false;
				this.DrawNode(g, this.LastClick);
			}
			node.Selected = true;
			this.LastClick = node;
			this.DrawNode(g, this.LastClick);
			node.Click(null);
			//Clean
			g.Dispose();
		}

		/// <summary>
		/// Removes the node.
		/// </summary>
		/// <param name="node">The node.</param>
		public void RemoveNode(MusicNode node) {
			bool found = false;
			foreach (MusicGroup group in _groups) {
				foreach (MusicNode mNode in group.Nodes) {
					if (node == mNode) {
						found = true;
						group.Nodes.Remove(node);
						break;
					}
				}
				if (found) {
					break;
				}
			}

			if (found) {
				this.LastClick = _groups[0].Nodes[0];
				_groups[0].Nodes[0].Selected = true;
				_groups[0].Nodes[0].Click(new MouseEventArgs(MouseButtons.Left, 0, 0, 0, 0));
				this.Rebound();
				this.Refresh();
				
			}
			
		}

		public MusicNode HitInfoNode(Point p) {
			for (int i = 0; i < _groups.Count; i++) {
				for (int n = 0; n < _groups[i].Nodes.Count; n++) {
					if (_groups[i].Nodes[n].Bounds.Contains(p)) {
						return _groups[i].Nodes[n];
					}
				}
			}
			return null;
		}
		public MusicGroup HitInfoGroup(Point p) {
			if (_groups != null && _groups.Count > 0) {
				for (int i = 0; i < _groups.Count; i++) {
					if (_groups[i].Bounds.Contains(p)) {
						return _groups[i];
					}
				}
			}
			return null;
		}
		#endregion
		#region Override
		protected override void OnPaint(PaintEventArgs e) {
			if (!this.DesignMode) {
				this.Draw(e.Graphics);
			}
		}
		protected override void OnMouseDown(MouseEventArgs e) {
			this.Focus();
			MusicNode n = HitInfoNode(e.Location);
			if (n != null && (n.Visible || !this.Expanded)) {
				if (n != this.LastClick) {
					Graphics g = this.CreateGraphics();
					if (this.LastClick != null) {
						LastClick.Selected = false;
						DrawNode(g, this.LastClick);
					}
					n.Selected = true;
					LastClick = n;
					DrawNode(g, n);
					n.Click(e);
					//Clean up
					g.Dispose();
					//Start effect
					EffectStart();
				} else {
					//This item is allready selected.
					Rectangle rect = new Rectangle(n.Bounds.X + 5, n.Bounds.Y + 5, n.Bounds.Width - 10, n.Bounds.Height - 10);
					if (rect.Contains(e.Location) && e.Button == MouseButtons.Left) {
						this.Rename();
					}
				}
				if (e.Button == MouseButtons.Right) {
					if (n.Tag is IPlaylist) {
						if (!((IPlaylist)n.Tag).Primary) {
							//Create context, and show
							this.CreateContext();
							_context.Show(MousePosition);
						}
					}
				}
			} else {
				//Maybe user clicked on group
				if (this.Expanded) {
					MusicGroup grp = this.HitInfoGroup(e.Location);
					if (grp != null) {
						grp.Toggle();
						this.Rebound();
						this.Refresh();
					} else {
						EffectStop();

						if (e.Button == MouseButtons.Right) {
							//Create context, and show
							this.CreateContext(true, false);
							_context.Show(MousePosition);
						}
					}
				}
			}
		}
		protected override void OnMouseUp(MouseEventArgs e) {
			
		}
		protected override void OnHandleCreated(EventArgs e) {
			base.OnHandleCreated(e);
			//Don't
			if (!this.DesignMode) {
				Rebound();
			}
		}
		protected override void OnSizeChanged(EventArgs e) {
			base.OnSizeChanged(e);
			if (!this.DesignMode) {
				this.Rebound();
			}
		}
		protected override void OnKeyDown(KeyEventArgs e) {
			base.OnKeyDown(e);
			if (e.KeyCode == Keys.F2) {
				this.Rename();
			}
		}
		#endregion
		#region Property
		/// <summary>
		/// Gets or sets the last clicked node.
		/// </summary>
		/// <value>MusicNode.</value>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public MusicNode LastClick {
			get { return _lastClick; }
			set { _lastClick = value; }
		}

		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public List<MusicGroup> Groups {
			get { return _groups; }
			set { _groups = value; }
		}
		public int TotalHeight {
			get {
				int height = 0;
				if (_groups != null && _groups.Count > 0) {
					for (int i = 0; i < _groups.Count; i++) {
						if (this.Expanded) {
							height += _groups[i].Bounds.Height;
						}
						if (_groups[i].Nodes != null && _groups[i].Nodes.Count > 0) {
							for (int n = 0; n < _groups[i].Nodes.Count; n++) {
								height += _groups[i].Nodes[n].Bounds.Height;
							}
						}
					}
				}
				return height;
			}
		}

		public bool Expanded {
			get {
				if (this.Width > 20) {
					return true;
				}
				return false;
			}
		}
		public bool ExpandedLibrary {
			get {
				return Config.Settings.Cache.Get<bool>("CollapsedLibrary", true);
			}
			set {
				Config.Settings.Cache.Set<bool>("CollapsedLibrary", value);
			}
		}
		public bool ExpandedPlaylist {
			get {
				return Config.Settings.Cache.Get<bool>("ExpandedLibrary", true);
			}
			set {
				Config.Settings.Cache.Set<bool>("ExpandedPlaylist", value);
			}
		}
		#endregion
		#region Ending Handling
		private void Music_MouseClick(object sender, MouseEventArgs e) {
			if (Config.Plugins.HasStorage) {
				Config.Library.Activate(Config.Library.Collection);
			}
		}
		private void Favorite_MouseClick(object sender, MouseEventArgs e) {
			if (Config.Plugins.HasStorage) {
				Config.Library.Activate(null);
			}
		}		
		/// <summary>
		/// Handles the MouseClick event of the Node control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
		private void Node_MouseClick(object sender, MouseEventArgs e) {
			if (sender is MusicNode) {
				MusicNode node = (MusicNode)sender;
				if (node.Tag != null) {
					if (node.Tag is IPlaylist) {
						if (Config.Plugins.HasStorage) {
							Config.Library.Activate((IPlaylist)node.Tag);
						}
						if (Config.Plugins.HasPlayControl) {
							Config.Plugins.PlayControl.Playlist = (IPlaylist)node.Tag;
						}
					}
				}
			}
		}

		private void TimerTick(object sender, EventArgs e) {
			if (this.LastClick != null) {
				EffectProvider();
			} else {
				EffectStop();
			}
		}
		private void TextBox_KeyDown(object sender, KeyEventArgs e) {
			if (e.KeyCode == Keys.Enter || e.KeyCode == Keys.Return) {
				//Updates node text, and removes itself.
				this.UpdateText();
				this.Controls.Remove((Control)sender);
			} else if (e.KeyCode == Keys.Escape) {
				EffectStart();
				this.Controls.Remove((Control)sender);
			}
		}

		/// <summary>
		/// Handles the LostFocus event of the TextBox control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void TextBox_LostFocus(object sender, EventArgs e) {
			//Updates node text, and removes itself.
			this.UpdateText();
			this.Controls.Remove((Control)sender);
		}

		/// <summary>
		/// Handles the Click event of the ItemDelete control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ItemDelete_Click(object sender, EventArgs e) {
			if (this.LastClick != null) {
				if (this.LastClick.Editable) {
					if (this.LastClick.Tag is IPlaylist) {
						IPlaylist pl = (IPlaylist)this.LastClick.Tag;
						Config.Plugins.Storage.Delete(pl);
						this.RemoveNode(this.LastClick);
					}
				}
			}
		}

		/// <summary>
		/// Handles the Click event of the ItemNew control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ItemNew_Click(object sender, EventArgs e) {
			if (Config.Plugins.HasStorage) {
				IPlaylist pl = new Playlist();
				pl.Name = "Playlist";
				MusicNode node = new MusicNode(pl.Name);
				node.Tag = pl;
				node.Editable = true;
				_groups[1].Nodes.Add(node);

				this.SubscribeNode(node);
				this.Rebound();
				this.SelectNode(node);
				this.Refresh();
				Config.Plugins.Storage.Put(pl);
			}
		}

		/// <summary>
		/// Handles the Closed event of the Context control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Windows.Forms.ToolStripDropDownClosedEventArgs"/> instance containing the event data.</param>
		private void Context_Closed(object sender, ToolStripDropDownClosedEventArgs e) {
			_context = null;
		}

		private void GroupLibrary_StateChanged(object sender, EventArgs e) {
			this.ExpandedLibrary = _groupLibrary.Expanded;
		}
		private void GroupPlaylist_StateChanged(object sender, EventArgs e) {
			this.ExpandedPlaylist = _groupPlaylist.Expanded;
		}
		#endregion
	}
}
